﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace Communication
{
    public class AsyncSender
    {
        private IPEndPoint endPoint;
        private Socket toServerSocket;

        public AsyncSender(IPAddress ipadd, int portNum)
        {
            this.endPoint = new IPEndPoint(ipadd, portNum);
        }

        public AsyncSender(IPEndPoint ipendpoint)
        {
            this.endPoint = ipendpoint;
        }
        
        private void CreateSocket()
        {
            try
            {
                if (endPoint != null)
                {
                    toServerSocket = new Socket(
                        AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                    toServerSocket.LingerState = new LingerOption(true, 10);
                }
                else
                {
                    Console.WriteLine("<Sender>Cannot create Socket with a null IPEndPoint.");
                }
            }
            catch (SocketException se)
            {
                Console.WriteLine("<Sender>Error creating Socket.\n" + se.Message);
            }
        }

        public void Send(string message)
        {
            CreateSocket();
            DoBeginConnect();
            DoBeginSend(message);
            sendDone.WaitOne();
            Shutdown();
        }

        private ManualResetEvent connectDone = new ManualResetEvent(false);
        private void DoBeginConnect()
        {
            try
            {
                connectDone.Reset();

                toServerSocket.BeginConnect(
                    endPoint.Address, endPoint.Port, new AsyncCallback(ConnectCallback), toServerSocket);
                // Wait here until the callback processes the connection.
                connectDone.WaitOne();
            }
            catch (SocketException s) { Console.WriteLine("<Sender>Connecting failed\n" + s); }
            catch (ArgumentNullException argnull) { 
                Console.WriteLine("<Sender> Value of Socket as null is not permited" + argnull.Message); }
        }

        private void ConnectCallback(IAsyncResult ar)
        {
            try
            {
                // Retrieve the socket from the state object.
                Socket socket = (Socket)ar.AsyncState;
                // Complete the connection.
                socket.EndConnect(ar);

                Console.WriteLine("<Sender>Socket connected to {0}",
                    socket.RemoteEndPoint.ToString());

                // Signal that the connection has been made.
                connectDone.Set();
            }
            catch (SocketException s)
            {
                Console.WriteLine("<Sender>Cannot connect.\n" + s.ToString());
            }
        }

        private ManualResetEvent sendDone = new ManualResetEvent(false);
        //after calling send method, use sendDone.WaitOne();
        private void DoBeginSend(string message)
        {
            // Convert the string data to byte data using ASCII encoding.
            byte[] byteData = Encoding.ASCII.GetBytes(message);

            // Begin sending the data to the remote device.
            toServerSocket.BeginSend(byteData, 0, byteData.Length, 0,
                new AsyncCallback(SendCallback), toServerSocket);
        }

        private void SendCallback(IAsyncResult ar)
        {
            try
            {
                Socket socket = (Socket)ar.AsyncState;
                // Complete sending the data to the remote device.
                int bytesSent = socket.EndSend(ar);

                // Signal that all bytes have been sent.
                sendDone.Set();
            }
            catch (SocketException e)
            {
                Console.WriteLine(e.ToString());
            }
        }

        private void Shutdown()
        {
            toServerSocket.Shutdown(SocketShutdown.Both);
        }

        public void Close()
        {
            Shutdown();
            toServerSocket.Close();
        }
    }
}
